/*
  flamingo - 2D Game Engine
  Copyright (C) 2009 Bradley Zeis

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  Bradley Zeis
  flamingoengine@gmail.com
*/

#define IMAGE       1

#include "IL/il.h"
#include "IL/ilu.h"

#include "flamingo.h"
#include "image.h"
#include <gl/gl.h>
#include <gl/glu.h>

PyTypeObject __declspec(dllexport) Texture_Type;

PyObject *core_module;
PyObject *LoadingError;

/* -------- UTILITY -------- */
PyObject *fl_bind(char *filename, ILuint *images, int length, int width, int height, int speed, int sheet) {

    Texture *tex = (Texture *)PyObject_CallFunction((PyObject *)&Texture_Type, "");
    tex->width = width;
    tex->height = height;
    tex->length = length;
    tex->speed = speed;
    tex->textures = PyMem_New(GLuint, tex->length);
    glGenTextures(tex->length, tex->textures);

    int i;
    for (i = 0; i < tex->length; i++) {
        if (sheet) {
            ilBindImage(images[i]);
        } else {
            ilActiveImage(i);
        }

        glBindTexture(GL_TEXTURE_2D, tex->textures[i]);
        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, width, height,
                          GL_RGBA, GL_UNSIGNED_BYTE, ilGetData());

        // Add later
        //for filter in filters:
        //    glTexParameterf(GL_TEXTURE_2D, filter[0], filter[1])
    }

    return (PyObject *)tex;
}

int uload(char *filename, int *width, int *height, int *length, int *speed) {

    /* Load Image */
    ILboolean b = ilLoadImage(filename);
    if (b == IL_FALSE) {
        // Might have to clear out all ilerrors
        ILenum err = IL_NO_ERROR, temp;

        while (1) {
            temp = ilGetError();
            if (temp == IL_NO_ERROR)
                break;
            err = temp;
        }

        PyErr_SetString(LoadingError, iluErrorString(err));
        return -1;
    }

    /* Extract Image Data */
    if (width != NULL)
        *width = ilGetInteger(IL_IMAGE_WIDTH);
    if (height != NULL)
        *height = ilGetInteger(IL_IMAGE_HEIGHT);
    if (length != NULL)
        *length = ilGetInteger(IL_NUM_IMAGES);
    if (speed != NULL)
        *speed = ilGetInteger(IL_IMAGE_DURATION);

    ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

    return 0;
}

/* -------- LOADING -------- */
PyObject *fl_load(PyObject *self, PyObject *args, PyObject *kwds) {

    char *filename;
    PyObject *filters=NULL;
    int speed=-1;
    static char *kwlist[] = {"filename", "speed", "filters", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|iO", kwlist, &filename, &speed, &filters))
        return NULL;

    /* Generate Image */
    ILuint image;
    ilGenImages(1, &image);
    ilBindImage(image);

    int result, width, height, anim_length, ospeed;
    result = uload(filename, &width, &height, &anim_length, &ospeed);
    if (result == -1)
        return NULL;

    if (speed < 0)
        speed = ospeed;

    /* Bind Image to OpenGL and return as Texture */
    PyObject *tex = fl_bind(filename, &image, anim_length + 1, width, height, speed, 0);
    ilDeleteImages(1, &image);
    return tex;
}

PyObject *fl_load_sheeth(PyObject *self, PyObject *args, PyObject *kwds) {

    char *filename;
    PyObject *filters=NULL;
    int speed=0, width;
    static char *kwlist[] = {"filename", "width", "speed", "filters", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "si|iO", kwlist, &filename, &width, &speed, &filters))
        return NULL;

    /* Generate Image */
    ILuint image;
    ilGenImages(1, &image);
    ilBindImage(image);

    int result, owidth, oheight;
    result = uload(filename, &owidth, &oheight, NULL, NULL);
    if (result == -1)
        return NULL;

    if (width == 0)
        width = 1;
    if (width > owidth)
        width = owidth;

    int length = owidth / width;

    /* Create subimages */
    ILuint images[length];
    ilGenImages(length, images);
    ILubyte *data = PyMem_Malloc(width * oheight * 4);

    int i;
    for (i = 0; i < length; i++) {
        ilBindImage(image);
        ilCopyPixels(width * i, 0, 0, width, oheight, 1, IL_RGBA, IL_UNSIGNED_BYTE, data);

        ilBindImage(images[i]);
        ilTexImage(width, oheight, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, NULL);
        ilSetData(data);
    }

    PyMem_Free(data);

    /* Bind Image to OpenGL and return as Texture */
    PyObject *tex = fl_bind(filename, images, length, width, oheight, speed, 1);
    ilDeleteImages(1, &image);
    ilDeleteImages(length, images);
    return tex;
}

PyObject *fl_load_sheetv(PyObject *self, PyObject *args, PyObject *kwds) {

    char *filename;
    PyObject *filters=NULL;
    int speed=0, height;
    static char *kwlist[] = {"filename", "height", "speed", "filters", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "si|iO", kwlist, &filename, &height, &speed, &filters))
        return NULL;

    /* Generate Image */
    ILuint image;
    ilGenImages(1, &image);
    ilBindImage(image);

    int result, owidth, oheight;
    result = uload(filename, &owidth, &oheight, NULL, NULL);
    if (result == -1)
        return NULL;

    if (height == 0)
        height = 1;
    if (height > oheight)
        height = oheight;

    int length = oheight / height;

    /* Create subimages */
    ILuint images[length];
    ilGenImages(length, images);
    ILubyte *data = PyMem_Malloc(owidth * height * 4);

    int i;
    for (i = 0; i < length; i++) {
        ilBindImage(image);
        ilCopyPixels(0, (oheight - height) - height * i, 0, owidth, height, 1, IL_RGBA, IL_UNSIGNED_BYTE, data);

        ilBindImage(images[i]);
        ilTexImage(owidth, height, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, NULL);
        ilSetData(data);
    }

    PyMem_Free(data);

    /* Bind Image to OpenGL and return as Texture */
    PyObject *tex = fl_bind(filename, images, length, owidth, height, speed, 1);
    ilDeleteImages(1, &image);
    ilDeleteImages(length, images);
    return tex;
}

/* -------- TEXTURE METHODS -------- */
/* Initialization */
void Texture_dealloc(Texture *self) {
    glDeleteTextures(self->length, self->textures);
    PyMem_Del(self->textures);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Texture_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Texture *self;
    self = (Texture *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->frame = 0;
        self->width = 0;
        self->height = 0;
        self->length = 0;
        self->speed = 0;
        self->textures = NULL;
        self->last_frame_switch = 0;
    }

    return (PyObject *)self;
}

int Texture_init(Texture *self, PyObject *args, PyObject *kwds) {
    return 0;
}

PyObject *Texture_hash(Texture *self) {
    return PyInt_FromLong(PyObject_Hash(PyInt_FromLong((int)self)));
}

/* Presentation */
PyObject *Texture_repr(Texture *self) {
    char string[64];
    sprintf(string, "<image.Texture(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *Texture_str(Texture *self) {
    return Texture_repr(self);
}

/* Sequence Methods */
Py_ssize_t Texture_len(Texture *self) {
    return self->length;
}

PyObject *Texture_item(Texture *self, Py_ssize_t i) {
    if (i >= self->length || i < 0) {
        PyErr_SetString(PyExc_IndexError, "Texture index out of range");
        return NULL;
    }

    return Py_BuildValue("i", self->textures[self->length]);
}

/* General Functions */
PyObject *Texture_update(Texture *self, PyObject *args) {
    if (self->speed == 0)
        Py_RETURN_NONE;

    int frames_advanced, dt;
    if (!PyArg_ParseTuple(args, "i", &dt))
        return NULL;

    frames_advanced = (self->last_frame_switch + dt) / self->speed;
    self->last_frame_switch = (self->last_frame_switch + dt) % self->speed;
    self->frame += frames_advanced;
    self->frame %= self->length;

    Py_RETURN_NONE;
}

/* Properties */
PyObject *Texture_getspeed(Texture *self, void *closure) {
    return Py_BuildValue("i", self->speed);
}

int Texture_setspeed(Texture *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        return -1;
    }
    int speed = PyInt_AsLong(value);
    self->speed = speed;
    return 0;
}

PyObject *Texture_getframe(Texture *self, void *closure) {
    return Py_BuildValue("i", self->frame);
}

int Texture_setframe(Texture *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        return -1;
    }
    int frame = PyInt_AsLong(value);
    self->frame = frame % self->length;
    return 0;
}

/* Method Declaration */
PyMemberDef Texture_members[] = {
    {"width", T_INT, offsetof(Texture, width), READONLY, NULL},
    {"height", T_INT, offsetof(Texture, height), READONLY, NULL},
    {"length", T_INT, offsetof(Texture, length), READONLY, NULL},
    {NULL}
};

PySequenceMethods Texture_SequenceMethods = {
    (lenfunc)Texture_len,                        /* sq_length */
    0,                                           /* sq_concat */
    0,                                           /* sq_repeat */
    (ssizeargfunc)Texture_item,                  /* sq_item */
    0,
    0,                                          /* sq_ass_item */
    0,                                          /* sq_contains */
    0,                                          /* sq_inplace_concat */
    0,                                          /* sq_inplace_repeat */
};

PyMethodDef Texture_methods[] = {
    {"update", (PyCFunction)Texture_update, METH_VARARGS, NULL},
    {"__hash__", (PyCFunction)Texture_hash, METH_NOARGS, NULL},
    {NULL}
};

PyGetSetDef Texture_getsets[] = {
    {"speed", (getter)Texture_getspeed, (setter)Texture_setspeed, NULL, NULL},
    {"frame", (getter)Texture_getframe, (setter)Texture_setframe, NULL, NULL},
    {NULL}
};

/* -------- TYPE DECLARATIONS -------- */
PyTypeObject Texture_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.image.Texture",                   /* tp_name */
    sizeof(Texture),                            /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Texture_dealloc,                /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Texture_repr,                     /* tp_repr */
    0,                                          /* tp_as_number */
    &Texture_SequenceMethods,                   /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Texture_str,                      /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Texture_methods,                            /* tp_methods */
    Texture_members,                            /* tp_members */
    Texture_getsets,                            /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Texture_init,                     /* tp_init */
    0,                                          /* tp_alloc */
    Texture_new,                                /* tp_new */
};

/* -------- MODULE INIT -------- */
PyMethodDef Image_methods[] = {
    {"load", (PyCFunction)fl_load, METH_VARARGS | METH_KEYWORDS, NULL},
    {"load_sheeth", (PyCFunction)fl_load_sheeth, METH_VARARGS | METH_KEYWORDS, NULL},
    {"load_sheetv", (PyCFunction)fl_load_sheetv, METH_VARARGS | METH_KEYWORDS, NULL},
    {NULL}
};

PyMODINIT_FUNC
initimage(void)
{
    PyObject *m;

    /* Imports */
    core_module = ImportModule("flamingo.core");
    if (core_module == NULL)
        Py_Exit(0);

    /* Initialize DevIL */
    ilInit();
    iluInit();
    ilEnable(IL_ORIGIN_SET);
    ilDisable(IL_BLIT_BLEND);

    /* Initialize module */
    if (PyType_Ready(&Texture_Type) < 0)
        return;

    m = Py_InitModule("image", Image_methods);
    if (m == NULL)
        return;

    Py_INCREF(&Texture_Type);
    PyModule_AddObject(m, "Texture", (PyObject *)&Texture_Type);
    LoadingError = PyErr_NewException("image.LoadingError", PyExc_Exception, NULL);
    Py_INCREF(LoadingError);
    PyModule_AddObject(m, "LoadingError", LoadingError);

    Py_DECREF(core_module);
}
